#include "net/Connector.h"
#include "net/EventLoop.h"
#include "net/TcpConnection.h"
#include "net/Socket.h"
#include "net/SockOpt.h"
#include "Base/Logger.h"
#include "Base/CurrectThread.h"
#include <errno.h>

namespace toym
{
namespace net
{

namespace detail
{



} // namespace detail


const int Connector::s_maxRetryDelayMs = 30*1000;
const int Connector::s_initRetryDelayMs = 500;


Connector::Connector(EventLoop* loop, const InetAddress& addr)
    : m_loop(loop),
      m_addr(addr),
      m_channel(),
      m_connectedCb(),
      m_state(State::eDisconnected),
      m_connected(false),
      m_retryDelayMs(s_initRetryDelayMs)
{
    LOG_DEBUG << "Connector::Connector [" << this << "] "; 
}

Connector::~Connector() {
    LOG_DEBUG << "Connector::~Connector [" << this << "] " ;
};

void Connector::start() {
    m_connected = true;
    m_loop->runInLoop([this](){
        this->startInLoop();
    });
}

void Connector::stop() {
    m_connected = false;
    m_loop->runInLoop([this](){
        this->stopInLoop();
    });
}

void Connector::startInLoop() {
    m_loop->assertInLoopThread();
    assert(m_state == State::eDisconnected);
    if (m_connected) {
        connect();
    } else {
        LOG_DEBUG << "do not connect";
    }
}

void Connector::stopInLoop() {
    m_loop->assertInLoopThread();
    if (m_state == State::eDisconnected) {
        setState(State::eDisconnected);
        int sockfd = removeAndResetChannel();
        retry(sockfd);
    }
}

void Connector::connect() {
    m_loop->assertInLoopThread();
    int sockfd = Socket::createSocket();
    int ret = SockOpt::connect(sockfd, m_addr.getAddr());
    if (ret != 0)
        CurrectThread::saveErrno(errno);
    switch(CurrectThread::getErrno()) {
    // 没有错误
    case 0:
    // socket是非阻塞的而且连接还没有立刻完成，可以使用select/poll来轮询其上的写操作来说明其连接完成；
    // 当select证明其可写之后，使用getsockopt(level=SOL_SOCKET, optname=SO_ERROR)来判断connect是成功（value=0), 还是失败（value是error code）
    case EINPROGRESS:
    // 被信号中断
    case EINTR:
    // socket早就已经连接了
    case EISCONN:
        CurrectThread::saveErrno(0);
        connecting(sockfd);
        break;

    // 路由缓存中的条目不足
    case EAGAIN:
    // 本地地址已被使用
    case EADDRINUSE:
    // sockfd引用的socket在之前没有bind到一个地址，在使用一个临时地址时，临时端口中所有的端口号都已经被使用
    case EADDRNOTAVAIL:
    // 流式的socket（TCP协议）远程地址没有被监听
    case ECONNREFUSED:
    // 网络不可达
    case ENETUNREACH:
        CurrectThread::saveErrno(0);
        retry(sockfd);
        break;
    // EACCES和EPERM对于TCP来说是一样的：
    //   用户尝试连接到一个广播地址而没有设置socket的广播flag
    //      或者因为本地防火墙规则的原因连接失败
    case EACCES:
    case EPERM:
    // 传递的地址中sa_family标志的address family不正确
    case EAFNOSUPPORT:
    // 套接字未阻塞，以前的连接尝试尚未完成
    case EALREADY:
    // sockfd是一个无效的打开的文件描述符
    case EBADF:
    // socket结构地址在用户的地址空间之外
    case EFAULT:
    // sockfd指向的不是一个套接字
    case ENOTSOCK:
        LOG_ERROR << "connect error in Connector::connect " << CurrectThread::getErrno() ;
        CurrectThread::saveErrno(0);
        Socket::close(sockfd);
        break;

    // 其他errno：
    //   EPROTOTYPE: The socket type does not support the requested communications protocol.  This error can occur, for example, on an attempt to connect a UNIX domain datagram  socket  to  a stream socket.
    //   ETIMEDOUT: Timeout  while  attempting  connection.   The server may be too busy to accept new connections.  Note that for IP sockets the timeout may be very long when syncookies are enabled on the server.
    default:
        LOG_ERROR << "Unexpected error in Connector::connect " << CurrectThread::getErrno() ;
        Socket::close(sockfd);
        CurrectThread::saveErrno(0);
        break;
    }
}

void Connector::connecting(int sockfd) {
    LOG_DEBUG << "Connector::connecting fd=" << sockfd;
    m_loop->assertInLoopThread();
    setState(State::eConnecting);
    m_channel.reset(new Channel(sockfd, m_loop));
    m_channel->setWriteCallback([this]() {
        this->handleWrite();
    });
    m_channel->setCloseCallback([this]() {
        this->handleError();
    });
    m_channel->enableWrite();
}

void Connector::handleWrite() {
    LOG_DEBUG << "Connector::handleWrite " << static_cast<int>(m_state);
    if (m_state == State::eConnecting) {
        int sockfd = removeAndResetChannel();
        int err = SockOpt::getSocketError(sockfd);
        if (err) { // != 0 , 有问题
            CurrectThread::saveErrno(err);
            LOG_WARNING << "Connector::handleWrite - SO_ERROR = " << err;
            CurrectThread::saveErrno(0);
            retry(sockfd);
        } else if (SockOpt::isSelfConnect(sockfd)) {
            LOG_WARNING << "Connector::handleWrite - Self connect";
            retry(sockfd);
        } else { // = 0 
            setState(State::eConnected);
            if (m_connected){
                if (m_connectedCb) {
                    m_connectedCb(sockfd);
                }
            } else {
                Socket::close(sockfd);
            }
        }
    }
}

void Connector::handleError() {
    LOG_ERROR << "Connector::handleError state=" << static_cast<int>(m_state);
    if (m_state == State::eConnecting)
    {
        int sockfd = removeAndResetChannel();
        int err = SockOpt::getSocketError(sockfd);
        CurrectThread::saveErrno(err);
        LOG_DEBUG << "SO_ERROR = " << err;
        CurrectThread::saveErrno(0);
        retry(sockfd);
    }
}

void Connector::retry(int sockfd) {
    Socket::close(sockfd);
    setState(State::eDisconnected);
    if (m_connected) {
        LOG_INFO << "Connector::retry - Retry connecting to " << m_addr.toIpPort()
             << " in " << m_retryDelayMs << " milliseconds. ";
        m_loop->runAt(Timestamp::now() + Timestamp(m_retryDelayMs),
                [this](){
                    this->startInLoop();
                }
        );
        m_retryDelayMs = std::min(m_retryDelayMs<<1, s_maxRetryDelayMs);
    } else {
        LOG_DEBUG << "not connect";
    }
}

int Connector::removeAndResetChannel() {
    int sockfd = m_channel->getFd();
    m_channel->disableAll();
    m_loop->removeChannel(*m_channel);
    m_loop->runInLoop(
        [this]() {
            this->resetChannel();
        }
    );
    return sockfd;
}

void Connector::resetChannel() {
    m_loop->assertInLoopThread();
    m_channel.reset();
}


} // namespace net

} // namespace toym
